#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         TextBox.py
#
# Copyright:    Copyright (c) 2005, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  A TextBox is a simple Component that permits capturing
#               of text imformation during the process of an assessment.
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.Component import Component, ComponentInfo
from Products.KnowledgeEngine.Assessible import Assessible
from Products.KnowledgeEngine.AssessmentComponentState import AssessmentComponentState
from Products.KnowledgeEngine.AssessmentComponentValueTypes import *
from Products.KnowledgeEngine.Exportable import Exportable
from Products.KnowledgeEngine.KERLExtensions import KERLExtensions
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.KERFProcessor import *
from Products.KnowledgeEngine.KERLProcessor import InvalidKerlOperator

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from App.ImageFile import ImageFile

#####################################################################
## Python Library Imports
# (none)

#####################################################################
## TextBoxInfo Class Definition
class TextBoxInfo(ComponentInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """

    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Return the class name
        """
        
        return 'TextBox'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Text Question"
        
    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            Return the identity type.  
        """
        
        return "text"
        
    #---------------------------------------------------------------#
    
    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """
        
        return ImageFile('skins/TextBox_icon.gif', globals())

    #---------------------------------------------------------------#

    def isInstanceOf(self, className):
        """
            Returns if the class is an instance of the specified
            className, or implements the interface specified 
            by the className
        """

        return className in ('Assessible', 'Exportable') or ComponentInfo.isInstanceOf(self, className)
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        ComponentInfo.onConfigureProperties(self)
        
        #add the 'reportContent' property        
        self.addPropertyDefinition(PropertyDefinition(identity="reportContent", displayName="Report Content", description="Text to be displayed for this component when used in a report", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_HTML, propertyStructure=ps_ATOMIC))        
        
#         #add the 'isMultiline' property        
#         self.addPropertyDefinition(PropertyDefinition(identity="isMultiline", displayName="Multiline Content?", description="Is the assessment content to span multiple lines or remain on a single line?", defaultValue=false, propertyType=pt_BOOLEAN, propertyFormat=pf_SELECTION + pf_YES_NO, propertyStructure=ps_ATOMIC))        
        
        #add the 'displayFormat' property
        self.addPropertyDefinition(PropertyDefinition(
            identity="displayFormat", 
            displayName="Display As", 
            description="What type of component should be displayed?", 
            propertyType=pt_STRING, 
            propertyFormat=pf_NATIVE, 
            hasEnumeratedValues=true, 
            enumeratedValuesList=["SingleLine","MultiLine","WYSIWYG"], 
            defaultValue="SingleLine",
        ))
        
        
        #add the 'defaultValue' property        
        self.addPropertyDefinition(PropertyDefinition(identity="defaultValue", displayName="Default Content", description="Initial text for new assessments", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_TEXT_MULTILINE, propertyStructure=ps_ATOMIC))        
        
        #add the 'visualWidth' property
        self.addPropertyDefinition(PropertyDefinition(identity="visualWidth", displayName="Visual Width", description="The visual width of the textbox when performing an assessment (decimal values acceptable)", defaultValue=0.0, propertyType=pt_FLOAT, propertyFormat=pf_NATIVE, propertyStructure=ps_ATOMIC))        

        #add the 'visualHeight' property
        self.addPropertyDefinition(PropertyDefinition(identity="visualHeight", displayName="Visual Height", description="The visual height of the textbox when performing an assessment (decimal values acceptable)", defaultValue=0.0, propertyType=pt_FLOAT, propertyFormat=pf_NATIVE, propertyStructure=ps_ATOMIC))        

        #add the 'visualUnitType' property
        self.addPropertyDefinition(PropertyDefinition(identity="visualMeasure", displayName="Visual Measurements Type", description="The type of measures used for the visual width and height of the textbox (CSS units)", defaultValue="em", propertyType=pt_STRING, propertyFormat=pf_NATIVE, propertyStructure=ps_ATOMIC, hasEnumeratedValues=true, enumeratedValuesList=['em', 'ex', 'px', 'in', 'cm', 'mm', 'pt', 'pc']))        
        
        #add the 'maximumLength' property
        self.addPropertyDefinition(PropertyDefinition(identity="maximumLength", displayName="Maximum Length", description="The maximum number of characters permitted (0 is unlimited)", defaultValue=0, propertyType=pt_INTEGER, propertyFormat=pf_NATIVE, propertyStructure=ps_ATOMIC))        

    #---------------------------------------------------------------#

#####################################################################
## TextBox Class Definition
class TextBox(Component, Assessible, KERLExtensions, Exportable):
    """
        Defines a simple component to represent one or more lines of 
        text entered during an assessment.
    """

    #---------------------------------------------------------------#

    #TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = TextBoxInfo()
    
    #---------------------------------------------------------------#

    # DTML Methods
    index_html = DTMLFile('dtml/index_html', globals())
    simplereport_html = DTMLFile('dtml/simplereport_html', globals())

    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the TextBox"        

        Component.__init__(self, id, identity)

    #---------------------------------------------------------------#

    def onAfterIdentityChange(self, identityMappings):
        """
            Handle Knowledge Engine specific actions after the 
            identity of this object has changed.
            
            The identityMappings dictionary holds 
            mappings from oldIdentities -> newIdentities
            (including this object)
                        
        """

        #perform default identity changes
        Component.onAfterIdentityChange(self, identityMappings)

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object 
            has been loaded into memory.  
            
            This is useful for performing backwards compatibility
        """

        #perform default onAfterInstanciation for the parent
        Component.onAfterInstanciation(self)

        #add visualWidth attribute if it doesn't already exist
        if not hasattr(self, "visualWidth"):
            self.visualWidth  = 0.0
            
        if not hasattr(self, 'visualHeight'):
            self.visualHeight = 0.0
            
        if hasattr(self, 'isMultiline') and not hasattr(self, 'displayFormat'):
            if self.isMultiline:
                self.displayFormat = 'MultiLine'
            else:
                self.displayFormat = 'SingleLine'
                        
            #if hasattr(self, "isMultiline") and self.getIsMultiline():
                #self.visualWidth = 30.0
            #else:
                #self.visualWidth = 20.0
            

        #add visualHeight attribute if it doesn't already exist
        #if not hasattr(self, "visualHeight"):
            ##are we single or multiple line?
            #if hasattr(self, "isMultiline") and self.getIsMultiline():
                #self.visualHeight = 6.2
            #else:
                #self.visualHeight = 1.2

        #add visualMeasure attribute if it doesn't already exist
        if not hasattr(self, "visualMeasure"):
            self.visualMeasure = "em"

        #add maximumLength attribute if it doesn't already exist
        if not hasattr(self, "maximumLength"):
            self.maximumLength = 0
                
    #---------------------------------------------------------------#

    def getVisualHeight(self):
        """
            Returns the visual width of the textbox
            If no size is set, return the default value depending
            on isMultiline
        """
        
        
        if self.visualHeight:
            return self.visualHeight
        elif self.getIsMultiline():
            return 20.0
        else:
            return 1.2
        
                
    #---------------------------------------------------------------#

    def getVisualWidth(self):
        """
            Returns the visual height of the textbox
            If no size is set, return the default value depending
            on isMultiline
        """
        
        if self.visualWidth:
            return self.visualWidth
        elif self.getIsMultiline():
            return 45.0 
        else:
            return 18.0
                
    #---------------------------------------------------------------#

    def getVisualMeasure(self):
        """
            Returns the visual measurement scale of the textbox
        """
        
        return self.visualMeasure
                
    #---------------------------------------------------------------#

    def getMaximumLength(self):
        """
            Returns the maximum number of characters in a textbox
        """
        
        return self.maximumLength
                
    #---------------------------------------------------------------#
        
    def getIsMultiline(self):
        "Returns if the textbox is multiline"        
        #return self.isMultiline
        return self.displayFormat == "MultiLine"
    
    #---------------------------------------------------------------#
    
    def getIsWYSIWYG(self):
        "Returns if the textbox is a wysiwyg"        
        #return self.isMultiline
        return self.displayFormat == "WYSIWYG"    
                
    #---------------------------------------------------------------#
    
    def getIsSingleline(self):
        "Returns if the textbox is singleline"        
        #return self.isMultiline
        return self.displayFormat == "SingleLine"    
                
    #---------------------------------------------------------------#
        
    def getDefaultValue(self):
        "Returns the component assessment default value"        
        return self.defaultValue

    #---------------------------------------------------------------#

    def setDefaultValue(self, defaultValue):
        "Set the component assessment default value"        
        self.defaultValue = defaultValue
        
    #---------------------------------------------------------------#
    
    def getField( self, *args, **kwargs ):
        """ 
            STUB - Although Kupu (WYSIWYG Editor) is written to work 
            without archetypes, it tries to call getField() on context 
            when initialized. We can safely return 'None' here and it
            will continue on it's merry way
        """
        
        return None
            
    
    
    #---------------------------------------------------------------#        

    def getComponentProperty(self, identity, propertyName, assessment = None):
        """
            Returns the value of the specified property, given the
            identity of the component (which may be used to lookup states) and
            an assessment.

            RETURNS: None if the property doesn't exist
        """

        if propertyName == "value":
            return assessment.getComponentState(identity).getValue()
        else:
            return Component.getComponentProperty(self, identity, propertyName, assessment)
       
    #---------------------------------------------------------------#
    # BEGIN: Assessible Implementation                              #
    #---------------------------------------------------------------#
        
    def getAssessmentValueType(self):
        """
            ABSTRACT: Returns the type of the value that is to be stored
            for this component when it's assessment state is persisted.
            
            NOTE 1: See AssessmentComponentValueTypes for a list of defined contants       
            NOTE 2: This method MUST be overriden in the implementing
            class to specify the correct type for value persistence
            
            RETURN TYPE: String
        """
        
        #textbox assessment values are strings
        return st_STRING
        
    #---------------------------------------------------------------#
        
    def getDefaultAssessmentState(self, assessment):
        """
            ABSTRACT: Constructs and returns a suitable 'default' 
            AssessmentComponentState for the provided Assessment
            instance.

            NOTE: This method MUST be overriden in the implementing
            class to provide a suitable default value for the component
            
            RETURN TYPE: AssessmentComponentState
        """
        
        return AssessmentComponentState(assessment.getIdentity(), assessment.getModelIdentity(), self.getIdentity(), self.getAssessmentValueType(), self.getDefaultValue(), revision=assessment.getRevision())
            
    #---------------------------------------------------------------#

    def updateAssessmentState(self, assessment, formValue, skipMandatory=False):
        """
            ABSTRACT: Updates the specified assessment using
            the provided HTTP REQUEST formValue.
            
            NOTE: This method MUST be overriden in the implementing
            class to suitably update the component state
        """
                       
            
        Component.updateAssessmentState( self, assessment, formValue, skipMandatory=skipMandatory )
        
        #get the current assessment state for the component from the assessment
        state = assessment.getComponentState(self.getIdentity())
        
        #trim to the maximum length
        if (self.getMaximumLength() > 0 and len(formValue) > self.getMaximumLength()):
            formValue = formValue[0:self.getMaximumLength()]
        
        if self.getIdentity() == 'text01173323039430345970121':
            print "Setting textbox to %s" % `formValue`
        
        #update the current state
        state.setValue(formValue)
        
        # If a searchIndex is defined, add that info to the component state
        searchIndex = getattr(self, 'searchIndex', False)
        if searchIndex:
            state.setSearchIndex(searchIndex)
            state.setSearchValue(formValue)
        
        #update the assessment
        assessment.setIsModified(true)
            
    #---------------------------------------------------------------#
    # END: Assessible Implementation                                #
    #---------------------------------------------------------------#
    
    #---------------------------------------------------------------#
    # BEGIN: KERLExtensions Implementation                          #
    #---------------------------------------------------------------#

    def compileKERLtoJavaScript(self, expression):
        """
            Returns a Python string that represents a JavaScript
            expression, which may be used to evaluate the status
            of the component class specific KERL expression.

            NOTE: this method is primarily used to produce
            JavaScript expressions that form the basis of the
            client-side showif technology.
            
            RETURN TYPE: String         
        """
        
        #example: cond="['txt1','contains', value]" returns 'document.getElementById("txt1").value.indexOf(value)>=0'
        #example: cond="['txt1','doesntcontain', value]" returns 'document.getElementById("txt1").value.indexOf(value)<0'
        #example: cond="['txt1','equals', value]" returns 'document.getElementById("txt1").value==value'
        #example: cond="['txt1','notequal', value]" returns 'document.getElementById("txt1").value!=value'
        #example: cond="['txt1', 'empty']" returns 'document.getElementById("txt1").value == ""'

        #get the identity (which is the identity of itself)        
        identity = self.getIdentity()

        #get the operator
        opr = expression[1]

        if opr == 'empty':
            return 'document.getElementById("%s").value == ""' % (identity, )

        if opr == 'notempty':
            return 'document.getElementById("%s").value != ""' % (identity, )

        #get the value
        value = asJavaScript(expression[2])

        #determine the result
        result = "document.getElementById(\"" + identity + "\").value"
        
        if opr == "contains":
            return result + ".indexOf(\"" + value + "\")>=0"
        
        elif opr == "doesntcontain":
            return result + ".indexOf(\"" + value + "\")<0"
        
        elif opr == "equals":
            return result + "==\"" + value + "\""
        
        elif opr == "doesntequal":
            return result + "!=\"" + value + "\""
        
        else:
            raise InvalidKerlOperator( opr )
            # return "true" 

    #---------------------------------------------------------------#

    def compileKERLtoEnglish(self, expression):
        """
            Returns a string representing an 'English' version
            of the component class specific KERL expression.

            NOTE: this method is primarily used to produce
            English descriptions of KERL expressions that may be 
            used to describe things like AssessmentFilters
            
            RETURN TYPE: String         
        """
        
        return ""

    #---------------------------------------------------------------#

    def evaluate(self, expression, assessment):
        """
            Evalutes the component class specific KERL expression
            using the provided assessment.

            NOTE: this method is primarily used to produce
            JavaScript expressions that form the basis of the
            client-side showif technology.

            RETURN TYPE: Boolean         
        """
        
        #get the identity
        identity = self.getIdentity()

        #get the stored state
        state = assessment.getComponentState(identity)

        #get the operator
        opr = expression[1]

        if opr == "empty":
            return state.getValue() in ("", None)
        
        if opr == "notempty":
            return state.getValue() not in ("", None)

        #get the value
        value = asJavaScript(expression[2])

        #evaluate the expression
        if opr == "contains":
            return state.getValue().find(value) >= 0
        
        elif opr == "doesntcontain":
            return state.getValue().find(value) == -1
        
        elif opr == "equals":
            return state.getValue() == value
        
        elif opr == "doesntequal":
            return state.getValue() != value
        
        else:
            #return true
            raise InvalidKerlOperator( opr )
    
    #---------------------------------------------------------------#

    def getDependencies(self, expression):
        """
            Returns a list of components that are required to
            evaluate the component class specific KERL expression.

            NOTE: Includes the specific component itself.

            RETURN TYPE: {Component} indexed by Component.getIdentity()
       """

        return {self.getIdentity():self}
    
    #---------------------------------------------------------------#

    def updateKERLIdentities(self, expression, identityMappings):
        """
            Returns the expression with the appropriate old identities
            (as identified by the identityMappings) replaced with 
            new identities.
        """
        
        #get the component identity from the expression (first element)
        identity = expression[0]
        
        #is the component in the identityMappings?
        if identityMappings.has_key(identity):
            expression[0] = identityMappings[identity]
        
        return expression

    #---------------------------------------------------------------#
    # END: KERLExtensions Implementation                            #
    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation

# Register Component Information with the Knowledge Engine
registerGenericObjectClass(TextBox)

#####################################################################
